#define MAX(a,b) (a>b?a:b)
#define MIN(a,b) (a<b?a:b)

#define TRACKRECEIVER_RECEIVE_LOG false
#define TRACKRECEIVER_PLAY_LOG true

#include "trackreceiver.hpp"
#include "../party-protocol/packet.hpp"
#include "../party-protocol/receiver.hpp"
#include "../party-protocol/channel.hpp"
#include <cstdio>
#include <map>
#include <pthread.h>
#include <string.h>

/*
 Logging:
 + #index l=a   Received packet from internet, current latency is a (before moving read index)
    a->b        Move read index from a to b (because current latency is too low)
 - #index       Read index and fed to the speaker
    _ #index    Deleted because we moved read index
 ... #index     Wait for packet from internet
 x #index       Created empty sound. Not waiting because latency is already high.
 */
Slab<soundsegment_content_size> soundSegmentContentPool = Slab<soundsegment_content_size>(220);
Slab<soundSegment> soundSegmentPool = Slab<soundSegment>(220);
Trackreceiver::Trackreceiver(){
    this->lock=PTHREAD_MUTEX_INITIALIZER;
}

void Trackreceiver::receiveSound(Packet* packet){
    soundSegment *s=new (soundSegmentPool.alloc()) soundSegment(packet->content.length);
    memcpy(s->addr, packet->content.addr, packet->content.length);
    int host_addr=packet->from_addr->sin_addr.s_addr;
    pthread_mutex_lock(&this->lock);
    if(this->allHosts.find(packet->from_addr->sin_addr.s_addr)==this->allHosts.end()){
        //Host not found
        //Create now
        this->allHosts[host_addr]=new singleHostTrack();
        this->soundMixer.addSource(this->allHosts[host_addr]);

    }
    singleHostTrack *host=this->allHosts[host_addr];
    pthread_mutex_unlock(&this->lock);

    //This is probably caused by resending, or high latency and the reader created empty sound.
    pthread_mutex_lock(&host->lock);
    if(packet->header->index<host->status.index){
        if(host->status.index-packet->header->index > 100){
            //Ridiculously far, probably that host restarted the app

            for(unsigned int i=packet->header->index;i<host->last_receive_index;i++)
                if(host->allSegments.find(i)!=host->allSegments.end()){
                    host->allSegments[i]->~soundSegment();
                    soundSegmentPool.release(host->allSegments[i]);
                    host->allSegments.erase(i);
                }
            host->status.index=packet->header->index;
            host->status.position=0;
            host->allSegments[packet->header->index]=s;
            host->last_receive_index=packet->header->index;

        }else{
            s->~soundSegment();
            soundSegmentPool.release(s);
            goto latency_control; // Don't insert a released segment into allSegments
        }
    }

    if(host->allSegments.find(packet->header->index)==host->allSegments.end())
        host->allSegments[packet->header->index]=s;
    else{
        //Received a resend
        s->~soundSegment();
        soundSegmentPool.release(s);
    }
latency_control:
    //Latency control
    host->last_receive_index=MAX(packet->header->index,host->last_receive_index);
    const unsigned int latency=host->last_receive_index>host->status.index ? host->last_receive_index-host->status.index : 0;
#if TRACKRECEIVER_RECEIVE_LOG
    printf("+ #%d l=%d\n",packet->header->index,host->last_receive_index-host->status.index);
#endif

    //Pull the read index if latency is too high
    if(latency>MAX_LATENCY){
        //High latency, skip forward
        const unsigned int target_index=host->last_receive_index>MAX_LATENCY ? host->last_receive_index-MAX_LATENCY : 0;
        for(unsigned int i=host->status.index;i<target_index;i++)
            if(host->allSegments.find(i)!=host->allSegments.end()){
#if TRACKRECEIVER_RECEIVE_LOG
                printf("  _ #%d\n",i);
#endif
                host->allSegments[i]->~soundSegment();
                soundSegmentPool.release(host->allSegments[i]);
                host->allSegments.erase(i);
            }
        host->status.index=target_index;
        host->status.position=0;
#if TRACKRECEIVER_RECEIVE_LOG
        printf("  %d->%d\n",host->status.index,target_index);
#endif
    }

    //Pull the read index if latency is steady and >0
    {
        static unsigned int latency_history[LATENCY_HISTORY_LENGTH]={0};
        static int history_index=0;
        latency_history[history_index]=latency;
        history_index++;
        if(history_index==LATENCY_HISTORY_LENGTH){
            unsigned int min_latency=(unsigned int)(-1);
            for(unsigned int i=0;i<LATENCY_HISTORY_LENGTH;i++)
                if(latency_history[i]<min_latency)
                    min_latency=latency_history[i];

            const unsigned int target_index=host->status.index+min_latency;
            for(unsigned int i=host->status.index;i<target_index;i++)
                if(host->allSegments.find(i)!=host->allSegments.end()){
#if TRACKRECEIVER_RECEIVE_LOG
                    printf("  _ #%d\n",i);
#endif
                    host->allSegments[i]->~soundSegment();
                    soundSegmentPool.release(host->allSegments[i]);
                    host->allSegments.erase(i);
                }

#if TRACKRECEIVER_RECEIVE_LOG
            printf("  %d->%d\n",host->status.index,target_index);
#endif
            host->status.index=target_index;
            history_index=0;
        }
    }


    pthread_mutex_unlock(&host->lock);
}


/*
 * readSound: read sound segments to somewhere
 * char* readto_address: where to write the read data
 * int length: how many bytes to read
 */
void singleHostTrack::readSound(char* readto_address, unsigned int length){
    readStatus *rs=&this->status;
    pthread_mutex_lock(&this->lock);
    while(length>0){
        if(this->allSegments.find(rs->index)==this->allSegments.end()){
            if(this->last_receive_index>rs->index && this->last_receive_index-rs->index>=MAX_LATENCY){
                //Latency is high, make blank sound
                soundSegment *nss=new (soundSegmentPool.alloc()) soundSegment(this->allSegments.empty()?length: this->allSegments.begin()->second->length);
#if TRACKRECEIVER_PLAY_LOG
                printf("x #%d\n",rs->index);
#endif
                memset(nss->addr, nosound, nss->length);
                this->allSegments[rs->index]=nss;
            }else{
                //Latency is low, wait for the sound to come.
                memset(readto_address, nosound, length);
#if TRACKRECEIVER_PLAY_LOG
                printf("... #%d\n",rs->index);
#endif
                break;
            }
        }
        unsigned int left_in_this_segment=this->allSegments[rs->index]->length-rs->position;
        if(left_in_this_segment<=length){
            memcpy(readto_address, this->allSegments[rs->index]->addr+rs->position, left_in_this_segment);
            readto_address+=left_in_this_segment;
            length-=left_in_this_segment;
            rs->index++;
            rs->position=0;
            this->allSegments[rs->index-1]->~soundSegment();
            soundSegmentPool.release(this->allSegments[rs->index-1]);
            this->allSegments.erase(rs->index-1);
#if TRACKRECEIVER_PLAY_LOG
            printf("- #%d\n",rs->index-1);
#endif
        }else{
            memcpy(readto_address, this->allSegments[rs->index]->addr+rs->position, length);
            readto_address+=length;
            rs->position+=length;
            length-=length;
        }
    }
    pthread_mutex_unlock(&this->lock);
}

void Trackreceiver::readSound(char *readto_address, unsigned int length){
    this->soundMixer.readSound(readto_address,length);
}

bool Trackreceiver::valid(){
    //Used to test whether there's still something to read here
    //Should return true if anyone of the track is valid
    pthread_mutex_lock(&this->lock);
    bool result=false;
    for(std::map<uint32_t,singleHostTrack*>::iterator host=this->allHosts.begin();host!=this->allHosts.end();host++){
        if(host->second->valid()){
            result=true;
        }
        if(result) break;
    }
    pthread_mutex_unlock(&this->lock);
    return result;
}
